#include <stdlib.h>
#include <stdarg.h>

#include <common/macro/build.h>
#include <common/macro/array.h>
#include <common/macro/debug.h>
#include <query/condition.h>
#include <query/query.h>
#include <query/result.h>
#include <storage/storage.h>
#include <storage/tuple/tuple.h>
#include <storage/tuple/page_tuple.h>

API_LIBLOCAL int query_filter_tuple_visible_origin(query_t *query, tuple_t *tuple) {
	transaction_phase_e phase;

	/* Let's see if the tuple origin is commited. */
	if (query->transaction->id != tuple->id_origin) {
		transaction_visibility(query->transaction, tuple->id_origin, &phase);
		if (phase != COMMITED) {
			debugmsg("Tuple is not marked as COMMITED.\n");
			return 0;
		}
	}

	/* Implied success. */
	debugmsg("No reasong to think we need to ditch this tuple.\n");
	return 1;
}

API_LIBLOCAL int query_filter_tuple_visible_create(query_t *query, tuple_t *tuple) {
	transaction_phase_e phase;

	/* Verify if we haven't actually created this tuple. */
	if (query->type == QUERY_UPDATE) {
		if ((query->iterator.tuple->id_insert == query->transaction->id) &&
		    (query->iterator.tuple->id_command == query->transaction->command_id) &&
			(query->iterator.tuple->id_command != 0)) {
			debugmsg("We've already scanned it.\n");
			return 0;
		}
	}

	/* Let's see if the tuple insert is commited. */
	if (query->transaction->id != tuple->id_insert) {
		transaction_visibility(query->transaction, tuple->id_insert, &phase);
		if (phase != COMMITED) {
			debugmsg("Tuple is not marked as COMMITED.\n");
			return 0;
		}
	}

	/* Implied success. */
	debugmsg("No reasong to think we need to ditch this tuple.\n");
	return 1;
}

API_LIBLOCAL int query_filter_tuple_visible_delete(query_t *query, tuple_t *tuple) {
	transaction_phase_e phase;

	debugmsg("SELF: %d\n", query->transaction->id);
	debugmsg("Verifying tuple (%u, %u, %u, %d).\n", tuple->id_insert, tuple->id_delete, tuple->id_command, tuple->deleted);

	/* Perhaps the tuple was deleted afterward? */
	if (tuple->deleted != TUPLE_EXISTING) {
		if (query->transaction->id != tuple->id_delete) {
			/* TODO: Add cache of future deletions to query. */

			transaction_visibility(query->transaction, tuple->id_delete, &phase);
			if (phase == COMMITED) {
				debugmsg("Tuple is marked as DELETED.\n");
				return 0;
			} else {
				debugmsg("Tuple not deleted, the transaction that attempted to aborted.\n");
				return 1;
			} 
		} else {
			debugmsg("We have deleted this tuple. Skip it.\n");
			/*
			 * The transaction is looking at a tuple it has previously deleted. Since transactions
			 * are supposed to be aware of the DML operations they make, we skip this tuple.
			 */
			return 0;
		}
	}

	/* Implied success. */
	debugmsg("No reasong to think we need to ditch this tuple.\n");
	return 1;
}

API_LIBLOCAL result_e query_filter_prepare(query_t *query) {
	int i, j;
		
	query->condition_values = NULL;

	for (i = 0; i < query->condition_lists_c; i++) {
		for (j = 0; j < query->condition_lists[i]->conditions_c; j++) {
			if (condition_bind(query->condition_lists[i]->conditions[j], query->transaction, query->table) != 0) {
				debugmsg("Oops!\n");
				return RESULT_CONDITIONERR;
			}
		}
	}

	/* Per-row cached data.*/
	ARRAY_ALLOC(query->condition_values, query->table->columns_c);
	for (i = 0; i < query->table->columns_c; i++) {
		query->condition_values[i] = NULL;
	}

	return RESULT_OK;
}

API_LIBLOCAL int query_filter_tuple_predicates(query_t *query) {
	int i, j, success;

	/* If there are no conditions whatsoever, relay it rightaway. */
	if (query->condition_lists_c == 0) {
		return 1;
	}

	/*
	 * Iterate through the condition lists. If at least
	 * one matches, emit the row/tuple.
	 */
	debugmsg("Querying conditions.\n");
	for (i = 0; i < query->condition_lists_c; i++) {
		/* Optimistically assume that the condition will match. */
		success = 1;

		for (j = 0; j < query->condition_lists[i]->conditions_c; j++) {
			if (!condition_check(query->condition_lists[i]->conditions[j], query, query->condition_values)) {
				/* Failure. */
				debugmsg("Failure of condition set %d.\n", i);
				success = 0;
				break;
			}
		}

		/* Emit the row if success. */
		if (success)
			break;
	}

	for (i = 0; i < query->table->columns_c; i++) {
		if (query->table->columns[i]->datatype->destroy != NULL)
			query->table->columns[i]->datatype->destroy(query->condition_values[i]);
		query->condition_values[i] = NULL;
	}

	return success;
}

API_LIBLOCAL int query_filter_finalize(query_t *query) {
	int i;

	/* Per-row cached data.*/
	if (query->condition_values != NULL) {
		for (i = 0; i < query->table->columns_c; i++) {
			free(query->condition_values[i]);
		}
		free(query->condition_values);
	}
	return 0;
}
